﻿using AuSys.Controllers;
using AuSys.Repos.Identities.Abstractions;
using AuSys.Services.App.UserInfo.Abstractions;
using Microsoft.AspNetCore.Mvc.Filters;
using RqEx = AuSys.Utils.Exceptions.RequestInvalidException;

namespace AuSys.Services.App.ActionFilters
{
    public class UserAccessFilter(
        IUserInfoService userInfoService,
        IBasicUserRepo userRepo
        ) : IAsyncActionFilter
    {
        public string? AccessArg { get; set; }
        public string? Policy { get; set; }
        public async Task OnActionExecutionAsync(
            ActionExecutingContext context, ActionExecutionDelegate next)
        {
            // 1.检查是否管理员
            var uinfo = userInfoService.GetUserInfo();
            if (uinfo.IsAdmin)
            {
                //管理员可绕过授权验证，直接通过
                await next();
                return;
            }

            // 2.检查Scope是否有对当前用户的设置
            context.HttpContext.Items.TryGetValue(
                UserAccessScopeFilter.userAccessScopeCheckedItemKey, out var uasChecked);
            if(uasChecked is bool uasCheckedBool && uasCheckedBool)
            {
                //在UserAccessScopeFilter中已通过检查，直接通过
                await next();
                return;
            }

            // 3.使用指定的action参数检查当前用户是否有权访问
            object? obj = null;
            var args = context.ActionArguments;
            if (AccessArg is not null)
            {
                if (args.TryGetValue(AccessArg, out var val))
                    obj = val;
                else
                    throw new Exception("权限验证参数异常(找不到)");
            }
            else
            {
                //若参数多于1个，则必须使用AccessArg进行指定
                //若参数仅有1个，直接取它
                if (args.Count == 1)
                    obj = args.First().Value;
            }
            if(obj is null)
                throw new Exception("权限验证参数异常(未指定)");
            var controller = context.Controller as IAccessCheckController
                ?? throw new Exception("控制器类型检查异常，必须实现IAccessCheckController");

            var uids = controller.GetAccessibleUserIds(obj, Policy);
            if (uids.NoNeedCheck)
            {
                await next();
                return;
            }

            var ands = uids.Ands?.FindAll(x => x > 0) ?? [];
            var ors = uids.Ors?.FindAll(x => x > 0) ?? [];
            if (ands.Count == 0 && ors.Count == 0)
            {
                throw new RqEx("权限验证参数异常(为0)");
            }
            var andsFulfilled = true;
            if(ands.Count > 0)
                andsFulfilled = ands.All(userRepo.IsDescendantOrSelfOfMe);
            var orsFulfilled = true;
            if(ors.Count > 0)
                orsFulfilled = ors.Any(userRepo.IsDescendantOrSelfOfMe);
            if(andsFulfilled && orsFulfilled)
                await next();
            else
                throw new RqEx("无权限，请咨询管理员");
        }
    }

    /// <summary>
    /// 用于当前用户(可能未登录)验证api访问权限的ActionFilter<br/>
    /// action所属的Controller必须实现IAccessCheckController<br/><br/>
    /// 获取action参数中的目标对象：<br/>
    /// - action参数只有1个时，自动取它<br/>
    /// - action参数多于1个时，则必须使用AccessArg属性指定参数名称<br/><br/>
    /// 目标对象将传给api的Controller取得其所有者的Id<br/>
    /// 并通过User类的树状关系确认是否可以访问<br/><br/>
    /// 注意：仅用于确认是否有访问权限，具体的操作是否合法需Repo判断
    /// </summary>
    /// <param name="policy">从Controller获取所有者Id时使用的暗号</param>
    /// <param name="accessArg">指定用于标识对象的action参数名称</param>
    [AttributeUsage(AttributeTargets.Method)]
    public class UserAccessAttribute(
        string? policy = null, string? accessArg = null
        ): Attribute, IFilterFactory
    {
        public bool IsReusable => false;

        public string? AccessArg { get; set; } = accessArg;
        public string? Policy { get; set; } = policy;
        public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
        {
            var f = serviceProvider.GetRequiredService<UserAccessFilter>();
            f.AccessArg = AccessArg;
            f.Policy = Policy;
            return f;
        }
    }
}
